import { Lyric } from '@services/data-types/common.type';
import { from, zip, timer, Subject, Subscription } from 'rxjs';
import { skip } from 'rxjs/internal/operators';

export interface BaseLyricLine {
  txt: string;
  txtCn: string;
}

export interface LyricLine extends BaseLyricLine {
  time: number;
}

interface Handler extends LyricLine {
  lineNum: number;
}
// 截取歌词三段时间戳
const timeRegex = /\[(\d{1,2}):(\d{2})(?:\.(\d{2,3}))?\]/;

export class WyyLyric {
  private lyc: Lyric;
  private curNum: number;
  private playing = false;
  private pausedTime: number;
  private startPlayTime: number;
  timer$: Subscription;
  lines: LyricLine[] = [];
  handler = new Subject<Handler>();
  constructor(lyc: Lyric) {
    this.lyc = lyc;
    this.init();
  }
  play(startTime = 0, skips = false) {
    if (!this.lines.length) {
      return;
    }
    if (!this.playing) {
      this.playing = true;
    }
    this.curNum = this.findCurNum(startTime);
    this.startPlayTime = Date.now() - startTime;
    if (!skips) {
      this.callHandler(this.curNum - 1);
    }
    if (this.curNum < this.lines.length) {
      this.clearTimer();
      this.playReset();
    }
  }
  togglePlay(playing: boolean) {
    const now = Date.now();
    this.playing = playing;
    if (playing) {
      const startPlayTime = (this.pausedTime || now) - (this.startPlayTime || now);
      this.play(startPlayTime, true);
    } else {
      this.stop();
      this.pausedTime = now;
    }
  }
  stop() {
    if (this.playing) {
      this.playing = false;
    }
    this.clearTimer();
  }
  seek(time: number) {
    this.play(time);
  }
  private init() {
    // 注意：接口返回的个别歌曲的歌词可能会出现" "这种情况，要把空格替换为空，否则后续歌词无法解析，会报错
    const lyric = this.lyc.tlyric.replace(/\s/g, '');
    if (lyric) {
      this.generateForeign();
    } else {
      this.generateLyric();
    }
  }
  private findCurNum(time: number): number {
    const index = this.lines.findIndex(line => line.time <= time);
    if (index === -1) {
      return this.lines.length - 1;
    } else {
      return index;
    }
  }
  private playReset() {
    const line = this.lines[this.curNum];
    const delay = line.time - (Date.now() - this.startPlayTime);
    this.timer$ = timer(delay).subscribe(() => {
      this.callHandler(this.curNum++);
      if (this.playing && this.curNum < this.lines.length) {
        this.playReset();
      }
    });
  }
  private callHandler(index: number) {
    if (index > 0) {
      this.handler.next({
        txt: this.lines[index].txt,
        txtCn: this.lines[index].txtCn,
        time: this.lines[index].time,
        lineNum: index
      });
    }
  }
  private makeLines(line: string, tLine = '') {
    const result = timeRegex.exec(line);
    if (result) {
      const txt = line.replace(timeRegex, '').trim();
      const txtCn = tLine ? tLine.replace(timeRegex, '').trim() : '';
      if (txt) {
        const thirdResult = result[3] || '00';
        const len = thirdResult.length;
        const realThirdResult = len > 2 ? parseInt(thirdResult, 0) * 10 : parseInt(thirdResult, 0) * 10;
        const time = Number(result[1]) * 1000 * 60 + Number(result[2]) * 1000 + realThirdResult;
        this.lines.push({
          txt,
          txtCn,
          time
        });
      }
    }
  }
  private generateLyric() {
    const lines = this.lyc.lyric.split('\n');
    lines.forEach(line => this.makeLines(line));
  }
  // 解析双语歌词
  private generateForeign() {
    const lines = this.lyc.lyric.split('\n');
    const tLines = this.lyc.tlyric.split('\n').filter(item => timeRegex.exec(item) !== null);
    const moreLine = lines.length - tLines.length;
    let tempArr = [];
    if (moreLine >= 0) {
      tempArr = [lines, tLines];
    } else {
      tempArr = [tLines, lines];
    }
    const first = timeRegex.exec(tempArr[1][0])[0];
    const skipIndex = tempArr[0].findIndex(item => {
      const exec = timeRegex.exec(item);
      if (exec) {
        return exec[0] === first;
      }
    });
    const skips = skipIndex === -1 ? 0 : skipIndex;
    const skipItems = tempArr[0].slice(0, skips);
    if (skipItems.length) {
      skipItems.forEach(item => this.makeLines(item));
    }
    let zipLines$;
    if (moreLine > 0) {
      zipLines$ = zip(from(lines).pipe(skip(skips)), from(tLines));
    } else {
      zipLines$ = zip(from(lines), from(tLines).pipe(skip(skips)));
    }
    zipLines$.subscribe(([line, tLine]) => this.makeLines(line, tLine));
  }
  private clearTimer() {
    if (this.timer$) {
      this.timer$.unsubscribe();
      this.timer$ = null;
    }
  }
}
